package definition

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"text/template"

	config "gitee.com/woodpile/wgs.ths.config.go"
	"github.com/traefik/yaegi/interp" //cspell: disable-line
	"github.com/traefik/yaegi/stdlib" //cspell: disable-line
)

type ScriptConfig struct {
	GoPath    string
	ImportPkg []string

	PrepareExec string
	PrepareArgs []string
}

func (*ScriptConfig) Name() string {
	return "script"
}

func (*ScriptConfig) ToDefault(cfg config.IConfigDefault) {
	ac := cfg.(*ScriptConfig)
	ac.GoPath = "./script"
	ac.ImportPkg = []string{
		// "gitee.com/woodpile/wgs.ths.code.generate/outer/sample/script",
		"entry",
	}
}

type ScriptPackage struct {
	ConfigModifyFunc []FConfigModify

	ParsePreFunc  []FParsePre
	ParsePostFunc []FParsePost

	GeneratePreFunc  []FGeneratePre
	GeneratePostFunc []FGeneratePost

	TemplateFunc []FTemplateFunc
}

type FConfigModify func(cfg *Config) error
type FParsePre func(ctx *Context) error
type FParsePost func(ctx *Context) error
type FGeneratePre func(ctx *Context) error
type FGeneratePost func(ctx *Context) error
type FTemplateFunc func() template.FuncMap

// self package symbols
var Symbols = map[string]map[string]reflect.Value{}

func LoadScript(ctx *Context) error {
	if err := processPrepare(ctx); err != nil {
		return err
	}

	vm := interp.New(interp.Options{ //cspell: disable-line
		GoPath: GlobalConfig.Script.GoPath,
		Stdout: os.Stdout,
		Stderr: os.Stderr,
	})
	vm.Use(stdlib.Symbols) //cspell: disable-line
	vm.Use(Symbols)

	for _, pkg := range GlobalConfig.Script.ImportPkg {
		if _, err := vm.Eval(fmt.Sprintf("import \"%s\"", pkg)); err != nil {
			return fmt.Errorf("import package %s failed: %w", pkg, err)
		}

		base := filepath.Base(pkg)
		sp, err := vm.Eval(fmt.Sprintf("%s.Load()", base))
		if err != nil {
			return fmt.Errorf("load package %s failed: %w", base, err)
		}

		if err := loadScriptPackage(ctx, base, sp); err != nil {
			return err
		}
	}

	return nil
}

func loadScriptPackage(ctx *Context, name string, spr reflect.Value) error {
	if !spr.IsValid() || spr.IsNil() {
		fmt.Printf("Notice: script [%s] package got nil\n", name)
		return nil
	}

	sp := spr.Interface().(*ScriptPackage)
	// fmt.Printf("sp: %+v\n", sp)

	ctx.FuncConfigModify = append(ctx.FuncConfigModify, sp.ConfigModifyFunc...)
	ctx.FuncParsePre = append(ctx.FuncParsePre, sp.ParsePreFunc...)
	ctx.FuncParsePost = append(ctx.FuncParsePost, sp.ParsePostFunc...)
	ctx.FuncGeneratePre = append(ctx.FuncGeneratePre, sp.GeneratePreFunc...)
	ctx.FuncGeneratePost = append(ctx.FuncGeneratePost, sp.GeneratePostFunc...)
	ctx.FuncTemplateFunc = append(ctx.FuncTemplateFunc, sp.TemplateFunc...)

	return nil
}

func processPrepare(ctx *Context) error {
	if GlobalConfig.Script.PrepareExec == "" {
		return nil
	}

	err := exec.Command(GlobalConfig.Script.PrepareExec, GlobalConfig.Script.PrepareArgs...).Run()
	if err != nil {
		return fmt.Errorf("exec script prepare failed: %w", err)
	}

	return nil
}
